﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information
// 
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 

using System;
using System.IO;

using log4net.Util;
using log4net.Layout;
using log4net.Core;

namespace log4net.Appender
{
    /// <summary>Sends logging events to a <see cref="TextWriter"/>.</summary>
    /// <remarks>
    /// <para>
    /// An Appender that writes to a <see cref="TextWriter"/>.
    /// </para>
    /// <para>
    /// This appender may be used stand alone if initialized with an appropriate
    /// writer, however it is typically used as a base class for an appender that
    /// can open a <see cref="TextWriter"/> to write to.
    /// </para>
    /// </remarks>
    /// <author>Nicko Cadell</author>
    /// <author>Gert Driesen</author>
    /// <author>Douglas de la Torre</author>
    public class TextWriterAppender : AppenderSkeleton
    {
        /// <summary>Initializes a new instance of the <see cref="TextWriterAppender" /> class.</summary>
        /// <remarks>
        /// <para>
        /// Default constructor.
        /// </para>
        /// </remarks>
        public TextWriterAppender() 
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TextWriterAppender" /> class and
        /// sets the output destination to a new <see cref="StreamWriter"/> initialized 
        /// with the specified <see cref="Stream"/>.
        /// </summary>
        /// <param name="layout">The layout to use with this appender.</param>
        /// <param name="os">The <see cref="Stream"/> to output to.</param>
        /// <remarks>
        /// <para>
        /// Obsolete constructor.
        /// </para>
        /// </remarks>
        [Obsolete("Instead use the default constructor and set the Layout & Writer properties")]
        public TextWriterAppender(ILayout layout, Stream os) : this(layout, new StreamWriter(os))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TextWriterAppender" /> class and sets
        /// the output destination to the specified <see cref="StreamWriter" />.
        /// </summary>
        /// <param name="layout">The layout to use with this appender</param>
        /// <param name="writer">The <see cref="TextWriter" /> to output to</param>
        /// <remarks>
        /// The <see cref="TextWriter" /> must have been previously opened.
        /// </remarks>
        /// <remarks>
        /// <para>
        /// Obsolete constructor.
        /// </para>
        /// </remarks>
        [Obsolete("Instead use the default constructor and set the Layout & Writer properties")]
        public TextWriterAppender(ILayout layout, TextWriter writer) 
        {
            this.Layout = layout;
            this.Writer = writer;
        }

        /// <summary>
        /// Gets or set whether the appender will flush at the end 
        /// of each append operation.
        /// </summary>
        /// <value>
        /// <para>
        /// The default behavior is to flush at the end of each 
        /// append operation.
        /// </para>
        /// <para>
        /// If this option is set to <c>false</c>, then the underlying 
        /// stream can defer persisting the logging event to a later 
        /// time.
        /// </para>
        /// </value>
        /// <remarks>
        /// Avoiding the flush operation at the end of each append results in
        /// a performance gain of 10 to 20 percent. However, there is safety
        /// trade-off involved in skipping flushing. Indeed, when flushing is
        /// skipped, then it is likely that the last few log events will not
        /// be recorded on disk when the application exits. This is a high
        /// price to pay even for a 20% performance gain.
        /// </remarks>
        public bool ImmediateFlush 
        {
            get { return this.m_immediateFlush; }
            set { this.m_immediateFlush = value; }
        }

        /// <summary>Sets the <see cref="TextWriter"/> where the log output will go.</summary>
        /// <remarks>
        /// <para>
        /// The specified <see cref="TextWriter"/> must be open and writable.
        /// </para>
        /// <para>
        /// The <see cref="TextWriter"/> will be closed when the appender 
        /// instance is closed.
        /// </para>
        /// <para>
        /// <b>Note:</b> Logging to an unopened <see cref="TextWriter"/> will fail.
        /// </para>
        /// </remarks>
        public virtual TextWriter Writer 
        {
            get { return this.m_qtw; }
            set 
            {
                lock(this) 
                {
                    this.Reset();
                    if (value != null)
                    {
                        this.m_qtw = new QuietTextWriter(value, this.ErrorHandler);
                        this.WriteHeader();
                    }
                }
            }
        }

        /// <summary>This method determines if there is a sense in attempting to append.</summary>
        /// <remarks>
        /// <para>
        /// This method checks if an output target has been set and if a
        /// layout has been set. 
        /// </para>
        /// </remarks>
        /// <returns><c>false</c> if any of the preconditions fail.</returns>
        protected override bool PreAppendCheck() 
        {
            if (!base.PreAppendCheck()) 
            {
                return false;
            }

            if (this.m_qtw == null) 
            {
                // Allow subclass to lazily create the writer
                this.PrepareWriter();

                if (this.m_qtw == null) 
                {
                    this.ErrorHandler.Error("No output stream or file set for the appender named [" + this.Name + "].");
                    return false;
                }
            }
            if (this.m_qtw.Closed) 
            {
                this.ErrorHandler.Error("Output stream for appender named [" + this.Name + "] has been closed.");
                return false;
            }

            return true;
        }

        /// <summary>
        /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent)"/>
        /// method. 
        /// </summary>
        /// <param name="loggingEvent">The event to log.</param>
        /// <remarks>
        /// <para>
        /// Writes a log statement to the output stream if the output stream exists 
        /// and is writable.  
        /// </para>
        /// <para>
        /// The format of the output will depend on the appender's layout.
        /// </para>
        /// </remarks>
        protected override void Append(LoggingEvent loggingEvent) 
        {
            this.RenderLoggingEvent(this.m_qtw, loggingEvent);

            if (this.m_immediateFlush) 
            {
                this.m_qtw.Flush();
            } 
        }

        /// <summary>
        /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent[])"/>
        /// method. 
        /// </summary>
        /// <param name="loggingEvents">The array of events to log.</param>
        /// <remarks>
        /// <para>
        /// This method writes all the bulk logged events to the output writer
        /// before flushing the stream.
        /// </para>
        /// </remarks>
        protected override void Append(LoggingEvent[] loggingEvents) 
        {
            foreach(LoggingEvent loggingEvent in loggingEvents)
            {
                this.RenderLoggingEvent(this.m_qtw, loggingEvent);
            }

            if (this.m_immediateFlush) 
            {
                this.m_qtw.Flush();
            } 
        }

        /// <summary>Close this appender instance. The underlying stream or writer is also closed.</summary>
        /// <remarks>
        /// Closed appenders cannot be reused.
        /// </remarks>
        protected override void OnClose() 
        {
            lock(this)
            {
                this.Reset();
            }
        }

        /// <summary>
        /// Gets or set the <see cref="IErrorHandler"/> and the underlying 
        /// <see cref="QuietTextWriter"/>, if any, for this appender. 
        /// </summary>
        /// <value>
        /// The <see cref="IErrorHandler"/> for this appender.
        /// </value>
        public override IErrorHandler ErrorHandler
        {
            get { return base.ErrorHandler; }
            set
            {
                lock(this)
                {
                    if (value == null) 
                    {
                        LogLog.Warn(declaringType, "TextWriterAppender: You have tried to set a null error-handler.");
                    } 
                    else 
                    {
                        base.ErrorHandler = value;
                        if (this.m_qtw != null) 
                        {
                            this.m_qtw.ErrorHandler = value;
                        }
                    }	
                }
            }
        }

        /// <summary>This appender requires a <see cref="Layout"/> to be set.</summary>
        /// <value><c>true</c></value>
        /// <remarks>
        /// <para>
        /// This appender requires a <see cref="Layout"/> to be set.
        /// </para>
        /// </remarks>
        protected override bool RequiresLayout
        {
            get { return true; }
        }

        /// <summary>Writes the footer and closes the underlying <see cref="TextWriter"/>.</summary>
        /// <remarks>
        /// <para>
        /// Writes the footer and closes the underlying <see cref="TextWriter"/>.
        /// </para>
        /// </remarks>
        protected virtual void WriteFooterAndCloseWriter()
        {
            this.WriteFooter();
            this.CloseWriter();
        }

        /// <summary>Closes the underlying <see cref="TextWriter"/>.</summary>
        /// <remarks>
        /// <para>
        /// Closes the underlying <see cref="TextWriter"/>.
        /// </para>
        /// </remarks>
        protected virtual void CloseWriter() 
        {
            if (this.m_qtw != null) 
            {
                try 
                {
                    this.m_qtw.Close();
                } 
                catch(Exception e) 
                {
                    this.ErrorHandler.Error("Could not close writer [" + this.m_qtw + "]", e); 
                    // do need to invoke an error handler
                    // at this late stage
                }
            }
        }

        /// <summary>
        /// Clears internal references to the underlying <see cref="TextWriter" /> 
        /// and other variables.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Subclasses can override this method for an alternate closing behavior.
        /// </para>
        /// </remarks>
        protected virtual void Reset() 
        {
            this.WriteFooterAndCloseWriter();
            this.m_qtw = null;
        }

        /// <summary>Writes a footer as produced by the embedded layout's <see cref="ILayout.Footer"/> property.</summary>
        /// <remarks>
        /// <para>
        /// Writes a footer as produced by the embedded layout's <see cref="ILayout.Footer"/> property.
        /// </para>
        /// </remarks>
        protected virtual void WriteFooter() 
        {
            if (this.Layout != null && this.m_qtw != null && !this.m_qtw.Closed) 
            {
                string f = this.Layout.Footer;
                if (f != null)
                {
                    this.m_qtw.Write(f);
                }
            }
        }

        /// <summary>Writes a header produced by the embedded layout's <see cref="ILayout.Header"/> property.</summary>
        /// <remarks>
        /// <para>
        /// Writes a header produced by the embedded layout's <see cref="ILayout.Header"/> property.
        /// </para>
        /// </remarks>
        protected virtual void WriteHeader() 
        {
            if (this.Layout != null && this.m_qtw != null && !this.m_qtw.Closed) 
            {
                string h = this.Layout.Header;
                if (h != null)
                {
                    this.m_qtw.Write(h);
                }
            }
        }

        /// <summary>Called to allow a subclass to lazily initialize the writer</summary>
        /// <remarks>
        /// <para>
        /// This method is called when an event is logged and the <see cref="Writer"/> or
        /// <see cref="QuietWriter"/> have not been set. This allows a subclass to
        /// attempt to initialize the writer multiple times.
        /// </para>
        /// </remarks>
        protected virtual void PrepareWriter()
        {
        }

        /// <summary>
        /// Gets or sets the <see cref="log4net.Util.QuietTextWriter"/> where logging events
        /// will be written to. 
        /// </summary>
        /// <value>
        /// The <see cref="log4net.Util.QuietTextWriter"/> where logging events are written.
        /// </value>
        /// <remarks>
        /// <para>
        /// This is the <see cref="log4net.Util.QuietTextWriter"/> where logging events
        /// will be written to. 
        /// </para>
        /// </remarks>
        protected QuietTextWriter QuietWriter
        {
            get { return this.m_qtw; }
            set { this.m_qtw = value; }
        }

        /// <summary>
        /// This is the <see cref="log4net.Util.QuietTextWriter"/> where logging events
        /// will be written to. 
        /// </summary>
        private QuietTextWriter m_qtw;

        /// <summary>
        /// Immediate flush means that the underlying <see cref="TextWriter" /> 
        /// or output stream will be flushed at the end of each append operation.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Immediate flush is slower but ensures that each append request is 
        /// actually written. If <see cref="ImmediateFlush"/> is set to
        /// <c>false</c>, then there is a good chance that the last few
        /// logging events are not actually persisted if and when the application 
        /// crashes.
        /// </para>
        /// <para>
        /// The default value is <c>true</c>.
        /// </para>
        /// </remarks>
        private bool m_immediateFlush = true;

        /// <summary>The fully qualified type of the TextWriterAppender class.</summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private static readonly Type declaringType = typeof(TextWriterAppender);

        /// <summary>Flushes any buffered log data.</summary>
            /// <param name="millisecondsTimeout">The maximum time to wait for logging events to be flushed.</param>
            /// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns>
            public override bool Flush(int millisecondsTimeout)
            {
                // Nothing to do if ImmediateFlush is true
                if (this.m_immediateFlush)
                {
                    return true;
                }

                // lock(this) will block any Appends while the buffer is flushed.
                lock (this)
                {
                    this.m_qtw.Flush();
                }

                return true;
            }
    }
}
